package we3d 
{
	import flash.display.Sprite;
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import we3d.Scene3d;
	import we3d.layer.Layer;
	import we3d.renderer.IRenderer;
	import we3d.renderer.realistic.Default;
	import we3d.filter.BackgroundFilter;
	import we3d.core.Camera3d;
	import we3d.renderer.RenderInfo;
	
	/**
	* The View contains the rendered image. The View renders the Scene automatically if the Scene is invalid or every frame if forceUpdate is true. 
	* Use scene.invalidate() if something in the scene has changed. If autoRendering is disabled, you have to call the render method if you want to draw the scene.  
	* To display the rendered image, add  the <code>viewport</code> somewere to the stage. The <code>viewport</code> contains all layers of the view.  
	* The width and height of the view will be inherited to Layers if their size is zero. Note that the size of the Camera in the scene is not resized. You should keep the same width and height in View3d and Camera3d.
	*/
	public class View3d extends EventDispatcher 
	{
		/**
		* View3d constructor
		* @param	_width width for layers
		* @param	_height height for layers
		* @param	_mc viewport container
		* @param	_firstLayer optional set the first layer, default is a Bitmap Layer
		* @param	_firstLayerType the type of the first layer (BACKGROUND, SORTED or FOREGROUND)
		* @param	_renderer the renderer of the View, if null, the default renderer is used (renderer.realistic.Default)
		*/
		public function View3d (_width:Number=550, _height:Number=400, 
								_mc:Sprite=null, 
								_firstLayer:Layer=null, 
								_firstLayerType:String=SORTED, 
								_autoRender:Boolean=true, 
								_renderer:IRenderer=null) {
			if(_mc==null) {
				_mc = new Sprite();
				_mc.name = "viewport";
			}
			viewport = _mc;
			
			stTarget = new Sprite();
			bgTarget = new Sprite();
			fgTarget = new Sprite();
			
			viewport.addChild(bgTarget);
			viewport.addChild(stTarget);
			viewport.addChild(fgTarget);
			
			autoRendering = _autoRender;
			
			if(_autoRender) 
				if(viewport.stage != null) 
					stageAdded(null);
			
			width = _width;
			height = _height;
			
			lyrTable[SORTED]     = sortedLayers;
			lyrTable[BACKGROUND] = bgLayers;
			lyrTable[FOREGROUND] = fgLayers;
			
			tgtTable[SORTED]     = stTarget;
			tgtTable[BACKGROUND] = bgTarget;
			tgtTable[FOREGROUND] = fgTarget;
			
			if(!_firstLayer) _firstLayer = new Layer(true);
			firstLayer = _firstLayer;
			
			addLayer(_firstLayer, _firstLayerType);
			
			if(_renderer) renderer = _renderer;
		}
		
		/**
		* Event before the View renders a frame
		*/
		public static var EVT_BEGIN_DRAW:String = "evtBeginDraw";
		/**
		* Event after the View has rendered a frame
		*/
		public static var EVT_END_DRAW:String = "evtEndDraw";
		/**
		* Event when the View has cleared the previous render
		*/
		public static var EVT_CLEAR:String = "evtClear";
		/**
		* Layer type for addLayer method, Sorted layers are sorted with other layers by the average depth of all polygons in the layer
		*/
		public static const SORTED:String = "sorted";
		/**
		* Layer type for addLayer method, Foreground layers are always in front of background and sorted layers, multiple foreground layers appear in the order they have been added to the View
		*/
		public static const FOREGROUND:String = "foreground";
		/**
		* Layer type for addLayer method, Background layers are always behind all other layers, multiple background layers appear in the order they have been added to the View
		*/
		public static const BACKGROUND:String = "background";
		/**
		* Reference to the first Layer wich is created in the View3d constructor
		*/
		public var firstLayer:Layer;
		/**
		* Renderer of the view
		*/
		public var renderer:IRenderer=new Default;
		/**
		* Info from renderer
		*/
		public var renderInfo:RenderInfo=new RenderInfo;
		/**
		* Main target of all layers
		*/
		public var viewport:Sprite;
		/**
		* The scene to render
		*/
		public var scene:Scene3d;
		/**
		* If true, the scene will be redrawn every frame, otherwise only if the scene has been invalidated
		*/
		public var forceUpdate:Boolean=false;
		/**
		* If true, the screen will be cleared before every render, default is true
		*/
		public var autoClear:Boolean=true;
		/**
		* If camera is not null, the view uses its own camera for every scene it renders, otherwise the cam property of the scene
		*/
		public var camera:Camera3d=null;
		/**
		* The width of the view
		*/
		public var _width:Number = 550;
		/**
		* The height of the view
		*/
		public var _height:Number = 400;
		
		public function get width () :Number {
			return _width;
		}
		public function set width (w:Number) :void {
			if( w != _width) {
				_width = w;
				resizeLayers();
			}
		}
		public function get height () :Number {
			return _height;
		}
		public function set height (h:Number) :void {
			if(h != _height) {
				_height = h;
				resizeLayers();
			}
		}
		
		public function resizeLayers () :void {
			var L:int = allLayers.length;
			for(var i:int=0; i<L; i++) {
				if(allLayers[i].autoResize) {
					allLayers[i].setSize( _width, _height );
				}
			}
		}
		
		/**
		* @private
		* Target for depth-sorted layers
		*/
		public var stTarget:Sprite;
		/**
		* @private
		* Target for background layers
		*/
		public var bgTarget:Sprite;
		/**
		* @private
		* Target for foreground layers
		*/
		public var fgTarget:Sprite;
		/**
		* @private
		* Contains layers wich are sorted by depth
		*/
		public var sortedLayers:Array=[];
		/**
		* @private
		* Contains foreground layers
		*/
		public var fgLayers:Array=[];
		/**
		* @private
		* Contains background layers
		*/
		public var bgLayers:Array=[];
		/**
		* @private
		* contains all layers (sorted, bg and fg) in the order the layers have been added
		*/
		public var allLayers:Array=[];
		/**
		* @private
		*/
		public var currentLayer:Layer;
		/**
		* @private
		*/
		public var lyrTable:Object={};
		/**
		* @private
		*/
		public var tgtTable:Object={};
		/**
		* @private
		* Array with filters, default is null 
		*/
		public var filters:Array;
		/**
		* @private
		*/
		public var renderState:Boolean=false;
		
		private var _autoRendering:Boolean=false;
		
		/**
		* Invalidates the view for redraw
		*/
		public function invalidate () :void {
			renderState = true;
		}
		
		/**
		* Returns true if the scene can be rendered
		*/
		public function get isModified () :Boolean {
			return (scene && (scene.renderState || renderState));
		}
		/**
		* Render the scene 
		*/
		public function render () :void {
			if((forceUpdate && scene) || isModified) {
				if(camera != null) scene.cam = camera;
				if(autoClear) clear();
				renderer.draw(this);
				renderState = false;
			}
		}
		/**
		* Clears all layers from previous render 
		*/
		public function clear () :void {
			
			var L:int = allLayers.length;
			var i:int;
			for(i=0; i<L; i++) {
				if(allLayers[i].autoClear) allLayers[i].clear(this);
			}
			
			dispatchEvent(new Event(EVT_CLEAR));
		}
		/**
		* Add a filter 
		* @param	filter
		*/
		public function addFilter (filter:BackgroundFilter) :void {
			if(filters == null) {
				filters = [];
			}else{
				if(filters.indexOf(filter) >= 0) return;
			}
			filter.initialize(this);
			filters.push(filter);
		}
		/**
		* Remove a filter
		* @param	filter
		*/
		public function removeFilter (filter:BackgroundFilter) :void {
			if(filters != null) {
				var id:int = filters.indexOf(filter);
				if(id >= 0) {
					filter.remove(this);
					filters.splice(id, 1);
				}
			}
		}
		/**
		* Add a layer 
		* @param	lyr
		* @return
		*/
		public function addLayer (lyr:Layer, type:String=SORTED) :int {
			
			var id:int = layerId(lyr, type);
			if(id != -1) return id;
			
			id = lyrTable[type].push(lyr)-1;
			
			allLayers.push(lyr);
			
			lyr.initialize (this, tgtTable[type]);
			return id;
		}
		/**
		* Remove a layer
		* @param	lyr
		* @return
		*/
		public function removeLayer (lyr:Layer, type:String=SORTED) :Boolean {
			var id:int = layerId(lyr, type);
			if(id >= 0) {
				lyr.remove(this, tgtTable[type]);
				lyrTable[type].splice(id,1);
				id = allLayers.indexOf(lyr);
				if(id >= 0) allLayers.splice(id, 1);
				return true;
			}
			return false;
		}
		/**
		* Returns the id of layer
		* @param	lyr
		* @return
		*/
		public function layerId (lyr:Layer, type:String=SORTED) :int {
			return lyrTable[type].indexOf(lyr);
		}
		/**
		* Returns a layer by id 
		* @param	id
		* @return
		*/
		public function layerAt (id:int, type:String=SORTED) :Layer {
			return lyrTable[type][id];
		}
		/**
		* @private 
		*/
		public function initFrame (cf:Number) :void {
			
			dispatchEvent(new Event(EVT_BEGIN_DRAW));
			
			scene.initFrame(cf);
			
			var L:int = allLayers.length;
			var i:int;
			for(i=0; i<L; i++) {
				allLayers[i].initFrame(this);
			}
			
			if(filters != null) {
				var ft:BackgroundFilter;
				L = filters.length;
				for(i=0; i<L; i++) {
					ft = filters[i];
					if(ft.enabled) ft.initFrame(this);
				}
			}
		}
		/**
		* @private 
		*/
		public function endFrame (cf:Number) :void {
			
			scene.endFrame(cf);
			
			var L:int = allLayers.length;
			var i:int;
			for(i=0; i<L; i++) {
				allLayers[i].endFrame(this);
			}
			
			if(filters != null) {
				var ft:BackgroundFilter;
				L = filters.length;
				for(i=0; i<L; i++) {
					ft = filters[i];
					if(ft.enabled) ft.endFrame(this);
				}
			}
			
			L = sortedLayers.length;
			if(L > 1) {
				var lyrs:Array = sortedLayers.sortOn("totalAvg", Array.NUMERIC | Array.RETURNINDEXEDARRAY);
				var k:int=0;
				var lyr:Layer;
				for(i=0; i<L; i++) {
					lyr = sortedLayers[lyrs[i]];
					if(lyr.swapDepth && stTarget.contains(lyr.target)) stTarget.setChildIndex(lyr.target, k++);
				}
			}
			dispatchEvent(new Event(EVT_END_DRAW));
		}
		
		public function set autoRendering (value:Boolean) :void {
			if(value && !_autoRendering) {
				viewport.addEventListener(Event.ADDED, stageAdded);
				viewport.addEventListener(Event.REMOVED, stageRemoved);
				if(viewport.stage != null) stageAdded(null);
			}else if(!value && _autoRendering) {
				viewport.removeEventListener(Event.ADDED, stageAdded);
				viewport.removeEventListener(Event.REMOVED, stageRemoved);
				viewport.removeEventListener(Event.ENTER_FRAME, frameHandler);
			}
			_autoRendering = value;
		}
		public function get autoRendering () :Boolean {
			return _autoRendering;
		}
		
		private function stageAdded (e:Event) :void {
			if(e==null || e.target == viewport) viewport.addEventListener(Event.ENTER_FRAME, frameHandler);
		}
		private function stageRemoved (e:Event) :void {
			if(e==null || e.target == viewport) viewport.removeEventListener(Event.ENTER_FRAME, frameHandler);
		}
		private function frameHandler (e:Event) :void {
			if(scene == null) return;
			if(forceUpdate) scene.invalidate();
			render();
		}
	}
}